package helpers

import java.util.concurrent.TimeUnit

import helpers.ExtDateTimeFormat._
import org.mongodb.scala.bson._
import play.api.libs.json._
import play.api.libs.functional.syntax._

import scala.concurrent.duration.FiniteDuration
import scala.language.implicitConversions

trait Implicits2
    extends ShortType
    with DateCookies
    with JsValue2MDocumentImplicits
    with JodaTimeImplicits
    with BsonType2JsonImplicits

object Implicits2 extends Implicits2

trait ShortType {

  type MO = org.mongodb.scala.Document
  val MO = org.mongodb.scala.Document

  type ObjectId     = org.mongodb.scala.bson.ObjectId
  type BsonObjectId = org.mongodb.scala.bson.BsonObjectId
  type BsonDateTime = org.mongodb.scala.bson.BsonDateTime
  type BsonDouble   = org.mongodb.scala.bson.BsonDouble
  type BsonNumber   = org.mongodb.scala.bson.BsonNumber

  type Date       = java.util.Date
  type DateTime   = org.joda.time.DateTime
  type LocalDate  = org.joda.time.LocalDate
  type DateFormat = java.text.DateFormat

  type JsonParseException = com.fasterxml.jackson.core.JsonParseException
}

trait DateCookies extends ShortType {

  import scala.concurrent.duration._

  object now {

    def mill: Long = System.currentTimeMillis()

    def sec: Long = mill / 1000
  }

  implicit class Formater(f: DateFormat) {

    def formatNow: String = f.format(new Date)
  }

  implicit def Duration2Date(d: Duration): Date = new Date(d.toMillis)

  implicit def Date2Duration(d: Date): FiniteDuration = d.getTime.millis

  implicit def Duration2DateTime(d: Duration): DateTime = new DateTime(d.toMillis)

  implicit def DateTime2Duration(d: DateTime): Long = d.getMillis

  implicit def Millis2DateTime(mills: Long): DateTime = new DateTime(mills)

  implicit def DateTime2Date(d: DateTime): Date = d.toDate

  implicit def Date2DateTime(d: Date): DateTime = new DateTime(d)

  implicit def Formatter2DateTime(text: String): DateTime =
    DefaultDateTimeFormatter.parseDateTime(text)

  implicit def Date2BsonDateTime(date: Date): BsonDateTime = BsonDateTime(date)

  implicit def DateTime2BsonDateTime(date: DateTime): BsonDateTime = BsonDateTime(date.getMillis)
}

trait JodaTimeImplicits extends DateCookies {

  implicit class ExtendJodaDateTime(dt: DateTime) {

    def millsOfDate: Long = dt.millisOfDay().withMinimumValue()

    def toDuration: FiniteDuration = FiniteDuration(dt.getMillis, TimeUnit.MILLISECONDS)
  }

  implicit val DateTimeFormat: Format[DateTime] =
    Format(JodaReads.jodaDateReads(DATETIMEZONE), JodaWrites.jodaDateWrites(DATETIMEZONE))
  implicit val LocalDateFormat: Format[LocalDate] =
    Format(JodaReads.jodaLocalDateReads(DATETIMEZONE), JodaWrites.jodaLocalDateWrites(DATETIMEZONE))
}

trait BsonType2JsonImplicits extends ShortType {

  //  implicit val ObjectIdFormat: Format[ObjectId] = Format(
  //    (__ \ "$oid").read[String].map { oid =>
  //      new ObjectId(oid)
  //    }, Writes[ObjectId] { obj =>
  //      Json.obj("$oid" -> obj.toString)
  //    }
  //  )

  implicit val ObjectIdStringFormat: Format[ObjectId] =
    Format.of[String].inmap[ObjectId](id ⇒ new ObjectId(id), _.toHexString)

  implicit val BsonObjectIdFormat: Format[BsonObjectId] = Format(
    (__ \ "$oid").read[String].map { oid =>
      BsonObjectId.apply(oid)
    },
    Writes[BsonObjectId] { obj =>
      Json.obj("$oid" -> obj.getValue.toString)
    }
  )

  //  implicit val BsonDateTimeFormat: Format[BsonDateTime] =
  //    Format.of[Long].inmap[BsonDateTime](BsonDateTime.apply, _.getValue)

  implicit val DateFormat: Format[BsonDateTime] = Format(
    (__ \ "$date").read[Long].map { date =>
      BsonDateTime(date)
    },
    Writes[BsonDateTime] { obj =>
      Json.obj("$date" -> obj.getValue)
    }
  )

  //  implicit val BsonDoubleFormat: Format[BsonDouble] =
  //    Format.of[Double].inmap[BsonDouble](BsonDouble.apply, _.getValue)

  //  implicit val DoubleFormat: Format[Double] =
  //    Format.of[BigDecimal].inmap[Double](_.setScale(1, RoundingMode.DOWN).doubleValue(), BigDecimal.apply)

}

trait JsValue2MDocumentImplicits extends ShortType {

  implicit class ExtendJsValue[T <: JsValue](json: T) {

    def toMDocument: MO = Document(Json.stringify(json))
  }

}
